package com.lagou.cl;

import com.alibaba.fastjson.JSON;
import com.lagou.cl.bootstrap.ServerProducerBootStrap;
import com.lagou.cl.config.Constant;
import com.lagou.cl.config.DriudPoolConf;
import com.lagou.cl.config.RpcProperties;
import com.lagou.cl.config.ZooKeeperProperties;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.nio.charset.StandardCharsets;

/**
 * @author 程  林
 * @date 2020-05-24 16:58
 * @description //服务提供者
 * @since V1.0.0
 */
@SpringBootApplication(scanBasePackages = {"com.lagou"})
public class SpringbootNettyProducerApplication implements CommandLineRunner {

	private static Logger logger = LoggerFactory.getLogger(SpringbootNettyProducerApplication.class);

	@Autowired
	private ServerProducerBootStrap serverProducerBootStrap;

	@Autowired
	private RpcProperties rpcProperties;

	@Autowired
	private CuratorFramework curatorFramework;

	@Autowired
	private ZooKeeperProperties zkProperties;

	private Boolean isCreateDataSourceConfig = false;


	public static void main(String[] args) {

		SpringApplication.run(SpringbootNettyProducerApplication.class);

	}

	/**
	 * 基于Zookeeper实现简易版服务的注册与发现机制(注册中心)
	 * <p>
	 * 1. 启动2个服务端，可以将IP及端口信息自动注册到Zookeeper
	 * 2. 客户端启动时，从Zookeeper中获取所有服务提供端节点信息，客户端与每一个服务端都建立连接
	 * 3. 某个服务端下线后，Zookeeper注册列表会自动剔除下线的服务端节点，客户端与下线的服务端断开连接
	 * 4. 服务端重新上线，客户端能感知到，并且与重新上线的服务端重新建立连接
	 */

	@Override
	public void run(String... args) throws Exception {

		//先启动服务端
		serverProducerBootStrap.startServer();


		//连接zk服务端
		curatorFramework.start();
		curatorFramework.blockUntilConnected();
		logger.info("服务器提供端：{} ，已连接上zookeeper服务端：{}", rpcProperties.getProducerName(), zkProperties.getZkHost() + ":" + zkProperties.getPort());

		//当前的服务事务唯一名字（ZNode名称）
		String producerZnodeName = "/" + Constant.PRODUCER_TYPE + "/" + rpcProperties.getProducerName();
		String producerZnodeData = rpcProperties.getHost() + ":" + rpcProperties.getPort();

		//创建临时节点
		curatorFramework.create()
				.creatingParentsIfNeeded()
				.withMode(CreateMode.EPHEMERAL)
				.forPath(producerZnodeName, producerZnodeData.getBytes(StandardCharsets.UTF_8));


		//创建一个配置中心的节点
		if (!isCreateDataSourceConfig) {
			//数据库的信息 配置中心的节点
			String zkConfigPath = "/" + Constant.ZKCONFIG + "/" + "database";
			Stat stat = curatorFramework.checkExists().forPath(zkConfigPath);
			if (null == stat) {
				DriudPoolConf driudPoolConf = new DriudPoolConf();
				driudPoolConf.setUrl("jdbc:mysql://120.24.255.12:3306/lagou");
				driudPoolConf.setUsername("root");
				driudPoolConf.setPassword("toor");
				String data = JSON.toJSONString(driudPoolConf);

				curatorFramework.create()
						.creatingParentsIfNeeded()
						.withMode(CreateMode.PERSISTENT)
						.forPath(zkConfigPath, data.getBytes(StandardCharsets.UTF_8));
				isCreateDataSourceConfig = true;
				logger.info(zkConfigPath + "创建成功");
			} else {
				logger.info(zkConfigPath + "已存在");
			}

		}

	}


	/**
	 * 第三种监听器的添加方式: Cache 的三种实现 实践
	 * Path Cache：监视一个路径下1）孩子结点的创建、2）删除，3）以及结点数据的更新。
	 * 产生的事件会传递给注册的PathChildrenCacheListener。
	 * Node Cache：监视一个结点的创建、更新、删除，并将结点的数据缓存在本地。
	 * Tree Cache：Path Cache和Node Cache的“合体”，监视路径下的创建、更新、删除事件，并缓存路径下所有孩子结点的数据。
	 * <p>
	 * 能监听所有的字节点 且是无限监听的模式 但是 指定目录下节点的子节点不再监听
	 */
	private static void setListeners(CuratorFramework curatorFramework, String path) throws Exception {

		//1.path Cache  连接  路径  是否获取数据
		PathChildrenCache childrenCache = new PathChildrenCache(curatorFramework, path, true);
		PathChildrenCacheListener childrenCacheListener = (client, event) -> {
			ChildData data = event.getData();
			logger.info("事件：{}", JSON.toJSONString(event));
			switch (event.getType()) {
				//添加子节点
				case CHILD_ADDED:
					logger.info("新增子节点事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
					break;
				//移除子节点
				case CHILD_REMOVED:
					logger.info("移除子节点事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
					break;
				//子节点的数据变化
				case CHILD_UPDATED:
					logger.info("子节点数据变化事件 -- 当前子节点路径：{} ，当前子节点的数据：{}", data.getPath(), new String(data.getData()));
					break;
				default:
					break;
			}
		};
		childrenCache.getListenable().addListener(childrenCacheListener);
		childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
	}

}
